/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "IHDRScene.h"
#include "ResourceFactory.h"
#include "core/core.h"

#include <sstream>
#include <dinput.h>
#include "Config.h"

IHDRScene::IHDRScene(void)
:	IScene(),
	hdrRenderTarget(NULL),
	hdrRenderTargetScaled(NULL),
	hdrRenderTargetHighPass(NULL),
	hdrRenderTargetBlurX(NULL),
	hdrRenderTargetBlurY(NULL),
	hdrLumCurrent(NULL),
	hdrLumLast(NULL),
	hdrRenderEffect(NULL),
	fExposure(0.4f), 
	fGamma(0.55f),
	fVignetting(1.0f),
	iCropWidth(0),
	iCropHeight(0),
	bRenderBloom(true),
	fFrameDelta(0.01f),
	firstDraw(true)
{
	for (int i=0; i<4; ++i) 
		hdrLuminance[i] = NULL;
}

IHDRScene::~IHDRScene(void)
{
}

void IHDRScene::setRenderBloom(bool value)
{
	bRenderBloom = value;
}

bool IHDRScene::init()
{
	// Load the hdr effect and assign the textures
	hdrRenderEffect = coreGetResourceFactory()->loadShaderEffect(coreGetWorkingDirectory() + "/media/hdrrenderer.fx");
	
	// Get the handles
	smallHDRTextureHighPass = hdrRenderEffect->getParameterByName("smallHDRTextureHighPass");
	blurTexture             = hdrRenderEffect->getParameterByName("blurTexture");
	textureA                = hdrRenderEffect->getParameterByName("TextureA");
	textureB                = hdrRenderEffect->getParameterByName("TextureB");
	renderBloom             = hdrRenderEffect->getParameterByName("renderBloom");
	hPixelOffset            = hdrRenderEffect->getParameterByName("PixelOffset");
	hExposureLevel          = hdrRenderEffect->getParameterByName("exposureLevel");
	hGammaLevel             = hdrRenderEffect->getParameterByName("gammaLevel");
	hVignettingStrength     = hdrRenderEffect->getParameterByName("vignettingStrength");
	hAVSampleOffsets        = hdrRenderEffect->getParameterByName("avSampleOffsets");
	hElapsedTime            = hdrRenderEffect->getParameterByName("elapsedTime");
	hBlurSamplerSize        = hdrRenderEffect->getParameterByName("blurSamplerSize");
	hTexWidthHeight         = hdrRenderEffect->getParameterByName("texWidthHeight");
	restore();

	// Config values
	Config* conf = coreGetConfig();

	// Get the exposure
	if (conf->isDefined("g_exposure"))
		fExposure = conf->getNumber<float>("g_exposure");
	
	// Get the gamma
	if (conf->isDefined("g_gamma"))
		fGamma = conf->getNumber<float>("g_gamma");

	return IScene::init();
}

void IHDRScene::release() 
{
	bInitialized = false;
/*
	coreGetResourceFactory()->free( hdrRenderTargetScaled );
	coreGetResourceFactory()->free( hdrRenderTargetHighPass );
	coreGetResourceFactory()->free( hdrRenderTargetBlurX );
	coreGetResourceFactory()->free( hdrRenderTargetBlurY );
	coreGetResourceFactory()->free( hdrLumCurrent );
	coreGetResourceFactory()->free( hdrLumLast );

	for (int i=0; i<4; ++i) 
		coreGetResourceFactory()->free( hdrLuminance[i] );
*/
}

void IHDRScene::restore() 
{
	math::Vector2i size = pRenderer->getBackBufferSize();

	eFormat targetFormat = A16B16G16R16F;
	eFormat blurFormat   = A16B16G16R16F;
	eFormat lumFormat    = R32F;
	// Create a fullscreen render target with 16bit floating point values
	hdrRenderTarget = ITexture::CreateRenderTarget(static_cast<int>(size.x), static_cast<int>(size.y), targetFormat);

	// Crop the scene texture so width and height are evenly divisible by 8.
    // This cropped version of the scene will be used for post processing effects,
    // and keeping everything evenly divisible allows precise control over
    // sampling points within the shaders.
	iCropWidth = static_cast<int>(size.x) - static_cast<int>(size.x) % 8;
    iCropHeight = static_cast<int>(size.y) - static_cast<int>(size.y) % 8;

	// Create a scaled render target for blurring
	hdrRenderTargetScaled   = ITexture::CreateRenderTarget( iCropWidth / 4, iCropHeight / 4, targetFormat );
	hdrRenderTargetHighPass = ITexture::CreateRenderTarget( iCropWidth / 4, iCropHeight / 4, targetFormat );
	hdrRenderTargetBlurX    = ITexture::CreateRenderTarget( iCropWidth / 4, iCropHeight / 4, blurFormat );
	hdrRenderTargetBlurY    = ITexture::CreateRenderTarget( iCropWidth / 4, iCropHeight / 4, blurFormat );

	hdrLumCurrent = ITexture::CreateRenderTarget(1, 1, lumFormat);
	hdrLumLast    = ITexture::CreateRenderTarget(1, 1, lumFormat);
	
	for (int i=0; i<4; ++i) {
		int iSampleLen = 1 << (2*i);
		hdrLuminance[i]    = ITexture::CreateRenderTarget(iSampleLen, iSampleLen, lumFormat);
	}

	hdrRenderEffect->addTexture(hdrRenderEffect->getParameterByName("hdrTexture"), hdrRenderTarget);
	hdrRenderEffect->addTexture(hdrRenderEffect->getParameterByName("smallHDRTexture"), hdrRenderTargetScaled);

	math::Vector2i btsize = math::Vector2i(hdrRenderTargetBlurY->getWidth(), hdrRenderTargetBlurY->getHeight());
	hdrRenderEffect->setInt(hBlurSamplerSize, &btsize.x, 2);
	math::Vector2i bbsize = pRenderer->getBackBufferSize();
	hdrRenderEffect->setInt(hTexWidthHeight, &bbsize.x, 2);
	bInitialized = true;
}

void IHDRScene::destroy()
{
	release();

	coreGetResourceFactory()->freeRes( hdrRenderEffect );
	coreGetResourceFactory()->freeRes( debuginfo );
}

void IHDRScene::update(double dt)
{
	fFrameDelta = max(0.1f, static_cast<float>(dt));
}

void IHDRScene::draw()
{
	// Draw the nodes
	pRootNode->drawNodes();
}

void IHDRScene::drawScene()
{
	if ( !preDraw() ) 
		return;

	if (firstDraw)  {
		pRenderer->bindRenderTarget( hdrLumLast );
		pRenderer->beginScene();
		pRenderer->clearScene(math::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
		pRenderer->endScene();
		pRenderer->bindRenderTarget( hdrLumCurrent );
		pRenderer->beginScene();
		pRenderer->clearScene(math::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
		pRenderer->endScene();
		firstDraw = false;
		return;
	}


	/////////////////////////////////////////
	// RENDER INTO HDR TEXTURE //
	/////////////////////////////////////////

	// Bind the render target to the renderTarget texture
	pRenderer->bindRenderTarget( hdrRenderTarget );
	//hdrRenderTarget->beginScene();
	
	draw();

//	hdrRenderTarget->endScene();

	pRenderer->beginScene();
	/////////////////////////////////////////
	// GET THE LUMINANCE VALUE FROM THE SCENE
	/////////////////////////////////////////
	calculateLuminance();

	/////////////////////////////
	// DOWNSAMPLE              //
	/////////////////////////////
	pRenderer->bindRenderTarget( hdrRenderTargetScaled );
	pRenderer->clearScene();
	float *pixelOffset = new float[2];

	if (hdrRenderEffect) {
		hdrRenderEffect->setTechnique("downsample");
		
		pixelOffset[0] = 1.0f/(float)pRenderer->getBackBufferSize().x;
		pixelOffset[1] = 1.0f/(float)pRenderer->getBackBufferSize().y;

		if (hdrRenderEffect->bind()) {
			hdrRenderEffect->setFloat( hPixelOffset, pixelOffset, 2 );

			// Draw a quad to fill the downsampled render target with the values of the first render pass
			for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i) {
				if (hdrRenderEffect->beginPass(i)) {
					hdrRenderEffect->setVariables();
					pRenderer->drawScreenQuad(hdrRenderTargetScaled->getWidth(), hdrRenderTargetScaled->getHeight());
					hdrRenderEffect->endPass();
				}
			}

			hdrRenderEffect->unbind();
		}
	}

	// Copy texture and filter high color values
	pRenderer->bindRenderTarget( hdrRenderTargetHighPass );
	pRenderer->clearScene();

	if (hdrRenderEffect) {
		hdrRenderEffect->setTechnique("highpass");
		hdrRenderEffect->bind();

		// Draw a quad to fill the downsampled render target with the values of the first render pass
		for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i) {
			hdrRenderEffect->beginPass(i);
			hdrRenderEffect->setVariables();
			pRenderer->drawScreenQuad(hdrRenderTargetHighPass->getWidth(), hdrRenderTargetHighPass->getHeight());
			hdrRenderEffect->endPass();
		}
		hdrRenderEffect->unbind();
	}


	/////////////////////////////
	// BLUR X                  //
	/////////////////////////////
	pRenderer->bindRenderTarget( hdrRenderTargetBlurX );
	pRenderer->clearScene(math::Vector4(0,0,0,1));

	if (hdrRenderEffect) {
		hdrRenderEffect->setTechnique("blurX");
		hdrRenderEffect->setTexture(smallHDRTextureHighPass, hdrRenderTargetHighPass);

		pixelOffset[0] = 1.0f/((float)hdrRenderTargetScaled->getWidth());
		pixelOffset[1] = 1.0f/((float)hdrRenderTargetScaled->getHeight());

		hdrRenderEffect->setFloat( hPixelOffset, pixelOffset, 2 );
		if (hdrRenderEffect->bind()) {
			for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i) {
				if (hdrRenderEffect->beginPass(i)) {
					hdrRenderEffect->setVariables();
					pRenderer->drawScreenQuad(iCropWidth/4, iCropHeight/4);
					hdrRenderEffect->endPass();
				}
			}
			hdrRenderEffect->unbind();
		}
	}

	/////////////////////////////
	// BLUR Y                  //
	/////////////////////////////

	pRenderer->bindRenderTarget( hdrRenderTargetBlurY );
	pRenderer->clearScene(math::Vector4(0,0,0,0));

	if (hdrRenderEffect) {
		hdrRenderEffect->setTechnique("blurY");
		hdrRenderEffect->setTexture(blurTexture, hdrRenderTargetBlurX );

		pixelOffset[0] = 1.0f/((float)hdrRenderTargetScaled->getWidth());
		pixelOffset[1] = 1.0f/((float)hdrRenderTargetScaled->getHeight());

		hdrRenderEffect->setFloat( hPixelOffset, pixelOffset, 2 );
		hdrRenderEffect->bind();

		for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i) {
			hdrRenderEffect->beginPass(i);
			hdrRenderEffect->setVariables();
			pRenderer->drawScreenQuad(iCropWidth/4, iCropHeight/4);
			hdrRenderEffect->endPass();
		}

		hdrRenderEffect->unbind();
	}

	delete[] pixelOffset;

	/////////////////////////////////////////
	// TONEMAPPING AND DISPLAY             //
	/////////////////////////////////////////

	// Reset the render target to the back buffer
	pRenderer->unbindRenderTarget();

	pRenderer->beginScene();
	// Render the texture to the cube
	// Clear the backbuffer to a blue color
	pRenderer->clearScene(math::Vector4(1,1,1,1));

	if (hdrRenderEffect) {
		hdrRenderEffect->setTechnique("tonemap");
		if (hdrRenderEffect->bind()) {
			hdrRenderEffect->setFloat( hExposureLevel, &fExposure, 1 );
			hdrRenderEffect->setFloat( hGammaLevel, &fGamma, 1 );
			hdrRenderEffect->setFloat( hVignettingStrength, &fVignetting, 1);
			hdrRenderEffect->setBoolean(renderBloom, &bRenderBloom, 1);
			hdrRenderEffect->setTexture(blurTexture, hdrRenderTargetBlurY);
			hdrRenderEffect->setTexture(textureA, hdrLumCurrent);//

			for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i) {
				if (hdrRenderEffect->beginPass(i)) {
					hdrRenderEffect->setVariables();
					// Draw a fullscreen quad with the hdr texture assigned and perform tonemapping
					pRenderer->drawScreenQuad();
					hdrRenderEffect->endPass();
				}
			}

			hdrRenderEffect->unbind();		
		}
	}
	else
		hdrRenderTarget->display();

	pRenderer->endScene();

	draw2DElements();

	postDraw();
}

void IHDRScene::calculateLuminance()
{
	static math::Vector2f sampleOffsets[16];
	static ITexture* temp;
	// Initialize the sample offsets for the initial luminance pass.
	static float tU = 1.0f / (3.0f * hdrLuminance[3]->getWidth());
    static float tV = 1.0f / (3.0f * hdrLuminance[3]->getWidth());
    
    int index=0;
    for (int x = -1; x <= 1; ++x ) {
        for (int y = -1; y <= 1; ++y ) {
            sampleOffsets[index].x = x * tU;
            sampleOffsets[index].y = y * tV;

            ++index;
        }
    }
	// Draw the scaled version of the scene into a 64x64 pixel luminance target
	hdrRenderEffect->setFloat(hAVSampleOffsets, &sampleOffsets[0].x, sizeof(sampleOffsets));
	texturePass("initialLuminance", hdrLuminance[3], hdrRenderTargetScaled);

	// Downscale the luminance into a 16x16 pixel luminance target
	getSampleOffsets(hdrLuminance[3]->getWidth(), hdrLuminance[3]->getHeight(), sampleOffsets);
	hdrRenderEffect->setFloat(hAVSampleOffsets, &sampleOffsets[0].x, sizeof(sampleOffsets));
	texturePass("downscaleLuminance", hdrLuminance[2], hdrLuminance[3]);

	// Downscale the luminance into a 4x4 pixel luminance target
	getSampleOffsets(hdrLuminance[2]->getWidth(), hdrLuminance[2]->getHeight(), sampleOffsets);
	hdrRenderEffect->setFloat(hAVSampleOffsets, &sampleOffsets[0].x, sizeof(sampleOffsets));
	texturePass("downscaleLuminance", hdrLuminance[1], hdrLuminance[2]);

	// Downscale the luminance into a 1x1 pixel luminance target and take the exponent value
	getSampleOffsets(hdrLuminance[1]->getWidth(), hdrLuminance[1]->getHeight(), sampleOffsets);
	hdrRenderEffect->setFloat(hAVSampleOffsets, &sampleOffsets[0].x, sizeof(sampleOffsets));
	texturePass("finalLuminance", hdrLuminance[0], hdrLuminance[1]);

	// Swap texture
	temp = hdrLumLast;
	hdrLumLast = hdrLumCurrent;
	hdrLumCurrent = temp;

	// Calculate the luminance value adaption
	hdrRenderEffect->setFloat(hElapsedTime, &fFrameDelta, 1);
	texturePass("calculateAdaptedLuminance", hdrLumCurrent, hdrLumLast, hdrLuminance[0]);
}

void IHDRScene::texturePass(const std::string &technique, ITexture *target, ITexture *texA, ITexture *texB)
{	
	pRenderer->bindRenderTarget( target );
	//pRenderer->clearScene();

	hdrRenderEffect->setTechnique( technique );
	
	hdrRenderEffect->setTexture( textureA, texA );
	
	if (texB) 
		hdrRenderEffect->setTexture( textureB, texB );
	
	if (hdrRenderEffect->bind()) {
		hdrRenderEffect->setVariables();
		for (int i=0; i<hdrRenderEffect->getNumPasses(); ++i)  {
			if (hdrRenderEffect->beginPass(i)) {
				pRenderer->drawScreenQuad(target->getWidth(), target->getHeight());
				hdrRenderEffect->endPass();
			}
		}
		hdrRenderEffect->unbind();
	}
}

void IHDRScene::getSampleOffsets(int iWidth, int iHeight, math::Vector2f sampleOffsets[])
{
    float tU = 1.0f / iWidth;
    float tV = 1.0f / iHeight;

    // Sample from the 16 surrounding points. Since the center point will be in
    // the exact center of 16 texels, a 0.5f offset is needed to specify a texel
    // center.
    int index=0;
    for( int y=0; y < 4; ++y ) {
        for( int x=0; x < 4; ++x ) {
            sampleOffsets[ index ].x = (x - 1.5f) * tU;
            sampleOffsets[ index ].y = (y - 1.5f) * tV;
                                                      
            ++index;
        }
	}
}

void IHDRScene::setExposure(float exposure)
{
	fExposure = exposure;
}

void IHDRScene::setGamma(float gamma)
{
	fGamma = gamma;
}
